﻿using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace TimeTravel.XnaLibrary.UI
{
	/// <summary>
	/// Represents a GUI control. This class is abstract.
	/// </summary>
	public abstract class Control
	{
		private bool mContainedMouse;
		protected string mName;
		protected bool mIsVisible; 
		protected bool mIsEnabled;
		protected object mTag;

		/// <summary>
		/// Gets or sets the location to place the control in.
		/// </summary>
		public Rectangle Location;
		/// <summary>
		/// Gets the control currently holding the mouse capture or null if there isn`t one.
		/// </summary>
		public static Control MouseHolder { get; private set; }
		/// <summary>
		/// Gets the point at which the mouse was captured or Point.Zero if the mouse has not been captured.
		/// </summary>
		public static Point CapturedMousePosition { get;  set; }

		/// <summary>
		/// Gets or sets a value indicating if this control should be drawn.
		/// </summary>
		public virtual bool IsVisible
		{
			get { return mIsVisible; }
			set { mIsVisible = value; }
		}

		/// <summary>
		/// Gets or sets a value indicating if this control should raise events.
		/// </summary>
		public virtual bool IsEnabled
		{
			get { return mIsEnabled; }
			set { mIsEnabled = value; }
		}

		/// <summary>
		/// Gets or sets the name of the control.
		/// </summary>
		public string Name
		{
			get { return mName; }
			set { mName = value; }
		}

		/// <summary>
		/// Gets or sets an object holding information about the control.
		/// </summary>
		public object Tag
		{
			get { return mTag; }
			set { mTag = value; }
		}

		/// <summary>
		/// Delegate used to raise events on the control class.
		/// </summary>
		public delegate void ControlEventHandler(object source, ControlEventArgs args);
		/// <summary>
		/// Raised when the mouse enters the control.
		/// </summary>
		public event ControlEventHandler MouseEnter;
		/// <summary>
		/// Raised when the mouse leaves the control.
		/// </summary>
		public event ControlEventHandler MouseLeave;
		/// <summary>
		/// Raised when the user clicks on the control.
		/// </summary>
		public event ControlEventHandler MouseClick;
		/// <summary>
		/// Raised when user starts dragging the control.
		/// </summary>
		public event ControlEventHandler MouseDragStarted;
		/// <summary>
		/// Raised when the user drags the control.
		/// </summary>
		public event ControlEventHandler MouseDrag;
		/// <summary>
		/// Raised when the used stops dragging the control.
		/// </summary>
		public event ControlEventHandler MouseDragEnded;

		/// <summary>
		/// Initializes a new Control instance.
		/// </summary>
		public Control()
		{
			Location = Rectangle.Empty;
			mIsVisible = true;
			mIsEnabled = true;
		}

		#region EventRaisers
		private void OnMouseEnter(ControlEventArgs args)
		{
			if (MouseEnter != null)
				MouseEnter(this, args);
		}

		private void OnMouseLeave(ControlEventArgs args)
		{
			if (MouseLeave != null)
				MouseLeave(this, args);
		}

		private void OnMouseClick(ControlEventArgs args)
		{
			if (MouseClick != null)
				MouseClick(this, args);
		}

		private void OnMouseDragStarted(ControlEventArgs args)
		{
			if (MouseDragStarted != null)
				MouseDragStarted(this, args);
		}

		private void OnMouseDragEnded(ControlEventArgs args)
		{
			if (MouseDragEnded != null)
				MouseDragEnded(this, args);
		}

		private void OnMouseDrag(ControlEventArgs args)
		{
			if (MouseDrag != null)
				MouseDrag(this, args);
		}
		#endregion
		
		/// <summary>
		/// Captures the mouse.
		/// </summary>
		public void CaptureMouse()
		{
			if (MouseHolder == null)
			{
				MouseHolder = this;
				MouseState state = Mouse.GetState();
				CapturedMousePosition = new Point(state.X, state.Y);
			}
		}

		/// <summary>
		/// Releases mouse capture.
		/// </summary>
		public void ReleaseCapture()
		{
			MouseHolder = null;
			CapturedMousePosition = Point.Zero;
		}

		/// <summary>
		/// Checks for and raises any events that happened on the control.
		/// </summary>
		/// <param name="currentState">The current mouse state.</param>
		/// <param name="previousState">The mouse state in the previous frame.</param>
		public virtual void CheckForEvents(MouseState currentState, MouseState previousState)
		{
			if (!mIsEnabled || !mIsVisible)
				return;

			if (Location.Contains(new Point(currentState.X, currentState.Y)))
			{
				if (!mContainedMouse)
				{
					OnMouseEnter(new ControlEventArgs(currentState));
					mContainedMouse = true;
				}
			}
			else
			{
				if (mContainedMouse)
				{
					OnMouseLeave(new ControlEventArgs(currentState));
					mContainedMouse = false;
				}
			}

			if (MouseHolder == null || MouseHolder == this)
			{
				if (currentState.LeftButton == ButtonState.Pressed && previousState.LeftButton == ButtonState.Released &&
					mContainedMouse)
				{
					OnMouseClick(new ControlEventArgs(currentState));
				}
				else if (currentState.LeftButton == ButtonState.Released && Control.MouseHolder == this)
				{
					OnMouseDragEnded(new ControlEventArgs(currentState));
				}
				else if (currentState.LeftButton == ButtonState.Pressed && previousState.LeftButton == ButtonState.Pressed &&
					mContainedMouse && Control.MouseHolder == null &&
					 (currentState.X != previousState.X || currentState.Y != previousState.Y))
				{
					OnMouseDragStarted(new ControlEventArgs(currentState));
				}
				else if (currentState.LeftButton == ButtonState.Pressed && previousState.LeftButton == ButtonState.Pressed &&
					mContainedMouse && 
					(currentState.X != previousState.X || currentState.Y != previousState.Y))
				{
					OnMouseDrag(new ControlEventArgs(currentState));
				}
			}

		}

		/// <summary>
		/// Draws the control using the specified SpriteBatch. SpriteBatch.Begin must have been called prior to calling this method.
		/// </summary>
		/// <param name="spriteBatch">The SpriteBatch to draw with.</param>
		public abstract void Draw(SpriteBatch spriteBatch);

		public override string ToString()
		{
			return string.Format("{0}: {1}", Name, Location);
		}
	}

	public class ControlEventArgs : EventArgs
	{
		public MouseState MouseState { get; set; }

		public ControlEventArgs(MouseState state)
		{
			MouseState = state;
		}
	}
}
